package com.norriekoelle.virtualsensors.client;

import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLDisplay;

import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.net.Uri;
import android.opengl.GLSurfaceView;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.os.Vibrator;
import android.text.format.DateFormat;
import android.util.Log;
import android.view.Display;
import android.view.GestureDetector;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.Surface;
import android.view.Window;
import android.view.WindowManager;

import com.norriekoelle.virtualsensors.R;
import com.norriekoelle.virtualsensors.client.BookDatabaseAdapter.Collection;
import com.norriekoelle.virtualsensors.client.experiment.Experiment;
import com.norriekoelle.virtualsensors.client.graphics.GLLayer;
import com.norriekoelle.virtualsensors.client.graphics.GLLayerStateManager;
import com.norriekoelle.virtualsensors.server.CommandInterface;
import com.norriekoelle.virtualsensors.shared.BookshelfObject;
import com.norriekoelle.virtualsensors.shared.LogToFile;
import com.norriekoelle.virtualsensors.shared.PairReceiver;
import com.norriekoelle.virtualsensors.shared.Receiver;
import com.threed.jpct.Object3D;

/**
 * Bookshelf activity displays a book collection on a {@link GLLayer} and handles user interaction. 
 * 
 * @author Marion Koelle (marionkoelle)
 */
public class BookshelfActivity extends ClientActivity implements SensorEventListener {

	private final String LOG = "LOG";
	private LogToFile log;	
	public static boolean doLog = false;
	public BookDatabaseAdapter dbAdapter;
	// TODO(norrielm): See if we need all these in here.
	private GLSurfaceView mGLView;
	private GLLayer renderer = null;
	// TODO(norrielm): Can we keep an interaction state object?
	private ScaleGestureDetector scaleDetector;
	private GestureDetector mGestureDetector;
	private GLLayerStateManager manager;	
	private float scaleFactor = 1.f;
	public float xpos = -1;
	public float ypos = -1;
	private Collection currentCollection; 
	private Object3D activeObject;
	private String activeObjectUrl;
	private boolean longpress = false;
	private boolean longpressFront = false;
	private boolean inPhoneOnlyMode = true;
	public static final String IN_PHONE_ONLY_MODE_KEY = "PhoneOnly";
	// Book data
	private static final int INVALID_POINTER_ID = -1;
	private int activePointerId = INVALID_POINTER_ID;
	private Experiment experiment;
	private BookshelfObject bookToFind = null;
	private String categoryToMove = null;
	private Vibrator v;
	private Collection moveToCollection = null;
	private boolean fingerTouching = false;
	
	private Receiver<BookshelfObject> doSearchTaskCallback = new Receiver<BookshelfObject>() {
		@Override public void accept(BookshelfObject bookName) {
			// Set the experiment book.
			bookToFind = bookName;
			categoryToMove = null;
			// TODO(norrielm): Specify category and make this text persist on the screen for the experiment.
			handleMessage("Find " + bookToFind);
		}
	};
	private Runnable endOfTaskCallback = new Runnable() {
		@Override public void run() {
			// Reset the experiment book.
			bookToFind = null;
			categoryToMove = null;
			BookshelfActivity.this.runOnUiThread(new Runnable() {
				@Override public void run() {
					experiment.doNextTask();
				}
			});
		}
	};
	private Runnable nextSystemCallback = new Runnable() {
		@Override public void run() {
			inPhoneOnlyMode = !inPhoneOnlyMode;
		}
	};
	private Receiver<String> logServerCallback = new Receiver<String>() {
		@Override public void accept(String message) {
			connectionManager.sendUDPMessage("", message);
		}
	};
	private PairReceiver<BookshelfObject, Collection> doCategoriseTaskCallback = new PairReceiver<BookshelfObject, Collection>() {
		@Override public void accept(BookshelfObject bookName, Collection category) {
			// Set the experiment book.
			bookToFind = bookName;
			categoryToMove = category.getName();
			handleMessage("Move " + bookToFind + " to " + categoryToMove);
		}
	};
	protected final Runnable onReadyThread = new Runnable() {
		@Override public void run() {
			setContentView(mGLView);
			// Initialise the sensor interaction detections.
			SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
			Sensor accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
			sensorManager.registerListener(BookshelfActivity.this, accelerometer, 
				SensorManager.SENSOR_DELAY_GAME);
			mGestureDetector = new GestureDetector(BookshelfActivity.this, 
				new ExtendedGestureListener());
			scaleDetector = new ScaleGestureDetector(BookshelfActivity.this, 
				new ScaleListener());
			// Set up the connection to the Kinect.
			//handleMessage("starting...");
			connectionManager.start();
			
			experiment = new Experiment(BookshelfActivity.this, 
					doSearchTaskCallback, doCategoriseTaskCallback, endOfTaskCallback, nextSystemCallback,
					logServerCallback, dbAdapter.getAllObjects(),  
				(inPhoneOnlyMode) ? Experiment.SystemType.PHONE_ONLY : Experiment.SystemType.KINECT, log);
			BookshelfActivity.this.runOnUiThread(experiment);
		}
	};
	
	protected void onCreate(Bundle savedInstanceState) {
//		if (master != null) {
//			copy(master);
//		}
		super.onCreate(savedInstanceState);
		requestWindowFeature(Window.FEATURE_NO_TITLE);
		getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
				WindowManager.LayoutParams.FLAG_FULLSCREEN);
		getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, 
				WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
		setContentView(R.layout.splashscreen);	

		// Set up the experiment condition.
		inPhoneOnlyMode = getIntent().getBooleanExtra(IN_PHONE_ONLY_MODE_KEY, true);
		createLog();
//		handleMessage("Application opening in :" + ((inPhoneOnlyMode) ? " phone mode." : 
//			" kinect mode."));
		
		// Initialise the database.		
		currentCollection = Collection.ALL;
		dbAdapter = new BookDatabaseAdapter(this);
		dbAdapter.open();

		// Initialise the view.
		mGLView = new GLSurfaceView(getApplication());
		mGLView.setEGLConfigChooser(new GLSurfaceView.EGLConfigChooser() {
			public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {
				/* Ensure that we get a 16bit framebuffer. Otherwise, we'll fall back to 
				 * Pixelflinger on some device (read: Samsung I7500) */
				int[] attributes = new int[] { EGL10.EGL_DEPTH_SIZE, 16, EGL10.EGL_NONE };
				EGLConfig[] configs = new EGLConfig[1];
				int[] result = new int[1];
				egl.eglChooseConfig(display, attributes, configs, 1, result);
				return configs[0];
			}
		});
		manager = new GLLayerStateManager();
		v = (Vibrator) getSystemService(VIBRATOR_SERVICE);
		// Execute the task to set up the UI.
		new AsyncTask<Void, Void, Void>() {			
			@Override
			protected Void doInBackground(Void... params) {
				renderer = new GLLayer(BookshelfActivity.this);
				renderer.attachStateManager(manager);
				mGLView.setRenderer(renderer);
				return null;
			}
			
			@Override
			public void onPostExecute(Void result){
				Log.e("LOG", "Ready!");
				new Handler().post(onReadyThread);
			}
		}.execute();
	}

	@Override
	protected void onPause() {
		super.onPause();
		mGLView.onPause();
		renderer.unloadAllTextures();
		finish();
		System.gc();
		mGLView.destroyDrawingCache();
	}

	@Override
	protected void onStop() {
		super.onStop();
		SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
		sensorManager.unregisterListener(this);
		mGLView = null;
		renderer = null;
		finish();
		System.gc();
		connectionManager.closeServer();
	}

	@Override 
	protected void onDestroy(){
		super.onDestroy();
		mGLView = null;
		android.os.Process.killProcess(android.os.Process.myPid());
		System.gc();
	}

	boolean exiting = false;
	@Override 
	public void finish() {
		AlertDialog.Builder d = new AlertDialog.Builder(this);
		d.setTitle("Exit")
		.setMessage("Are you sure you want to quit?")
		.setCancelable(true)
		.setPositiveButton("Yes", 
		new DialogInterface.OnClickListener() {
			public void onClick(DialogInterface dialog, int id) {
				if (log != null) {
					log.close();
					sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, 
							Uri.parse("file://" + Environment.getExternalStorageDirectory())));	
				}
				connectionManager.closeServer();
				exiting = true;
				BookshelfActivity.this.finish();
			}
		})
		.setNeutralButton("Cancel", 
			new DialogInterface.OnClickListener() {
				public void onClick(DialogInterface dialog,
						int id) {
					dialog.cancel();
				}
			});
		d.create();
		d.show();
		if (exiting) {
			super.finish();			
		}
	}

	// TODO(norrielm): Check what this is for.
	protected boolean isFullscreenOpaque() {
		return true;
	}

	/**
	 * Copy data from a master Activity.
	 * 
	 * Unused: Don't want to retain the database during experiments.
	 */
//	private void copy(Object src) {
//		try {
//			Logger.log("Copying data from master Activity!");
//			Field[] fs = src.getClass().getDeclaredFields();
//			for (Field f : fs) {
//				f.setAccessible(true);
//				f.set(this, f.get(src));
//			}
//		} catch (Exception e) {
//			// TODO(norrielm): check what's happening here, why we're copying the Activity and what 
//			// actual exception is being thrown.
//			throw new RuntimeException(e);
//		}
//	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		MenuInflater inflater = getMenuInflater();
		// Allow the user to filter books with the menu when in phone only condition.
		if (inPhoneOnlyMode) {
//			handleMessage("Displaying menu.");
			inflater.inflate(R.menu.menu, menu);
		}
		return true;
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		// Handle item selection
		switch (item.getItemId()) {
//		case R.id.createLogfile:
//			//Zoom in	
//			createLog();
//			doLog(true);
//			return true;
		// TODO(norrielm): See if we can move these into a submenu.
//		case R.id.collection0:
//			showCollection(Collection.ALL);
//			return true;
		case R.id.collection1:
			showCollection(Collection.INFERENCE);
			return true;
		case R.id.collection2:
			showCollection(Collection.DYNAMICS);
			return true;
		case R.id.collection3:
			showCollection(Collection.INTERACTION);
			return true;
		default:
			return super.onOptionsItemSelected(item);
		}
	}
 
	private void showCollection(Collection c) {
		renderer.showCollection(c);
		v.vibrate(20);
		currentCollection = c;
	    log.writeWTime("Collection changed to " + c.getName());
	    Log.d(LOG, "Collection changed to " + c.getName());
	}

	/**
	 * Start an intent to view the currently selected book.
	 */
	public void startReading() {
		// Called once the open animation has completed.
//		handleMessage("Book opening:" + activeObjectUrl);
		Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(activeObjectUrl));
		startActivity(intent);
	}

	public BookDatabaseAdapter getDBAdapter() {
		return dbAdapter;
	}

	@Override
	public void handleMessage(String message) {
		if (renderer != null) {
			renderer.setDisplayMessage(message);
		}
		if (log != null) {
			log.writeWTime(message);
		}
		connectionManager.sendUDPMessage("", message);
	}

	@Override
	public void doLog(boolean isLogOn) {
		if(isLogOn){
			if (log != null) {
				log.close();
			}

			log = new LogToFile("Accel-" + System.currentTimeMillis());
			sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, 
					Uri.parse("file://" + Environment.getExternalStorageDirectory())));
			Log.d("SensorLogger", "New logfile created");
		}
		doLog = isLogOn;
	}

	private void createLog() {
		// Tell the Kinect to create a log file too.
		connectionManager.sendUDPMessage("", "log");
		
		if (log != null) {
			synchronized(log) { 
				log.close();
			}
		} 
		log = new LogToFile("Exp-" + DateFormat.format("hhmmss", new Date()));
		sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, 
				Uri.parse("file://" + Environment.getExternalStorageDirectory())));
	}

	@Override
	public void handleCommand(String command) {
//		Log.d(LOG, command);
		CommandInterface.commands c = CommandInterface.handleKinectMessage(command);
		switch(c){
		case TRACKING_ON:			
			break;
		case NEW_USER:
			Log.i(LOG, "New user detected");
			break;
		case LOST_USER:
			Log.i(LOG, "Lost user");
			break;
		case CLICK:
			break;
		case SELECT:
			break;
		case PUSH:
			break;
		case RAISE_HAND:
			break;
		case CIRCLE:
			System.out.println("Circle");
			break;
		case WAVE:
			break;
		case SWIPE_UP:
			break;
		case SWIPE_DOWN:
			break;
		case SWIPE_L:
//			Log.d(LOG, "Swipe L");
//			if (touchActive) {
//				Log.d(LOG, "scrolling left");
//				scrollLeft(20);
//			}
			// TODO(norrielm): what's this for?
			//			for (int i = 0; i <30; i++) {
			//				scrollLeft();	
			//			}
			break;
		case SWIPE_R:
//			Log.d(LOG, "swipe right");
//			if (touchActive) {
//				Log.d(LOG, "scrolling right");
//				scrollRight(40);
//				//				for (int i = 0; i <30; i++) {
//				//					scrollRight();	
//				//				}
//			}
			break;
		case HOTSPOT:
			if (!inPhoneOnlyMode) {
				Log.d(LOG, "Received hotspot command " + command + " while current collection is " + 
						currentCollection.ordinal());
				if (!longpressFront){
					// No book is currently selected, we just want to show the collection.
					if (command.contains("1") && !currentCollection.equals(Collection.INFERENCE)) {
						currentCollection = Collection.INFERENCE;
						showCollection(currentCollection);
					} else if (command.contains("2")&& !currentCollection.equals(Collection.DYNAMICS)){
						currentCollection = Collection.DYNAMICS;
						showCollection(currentCollection);
					} else if (command.contains("3") && !currentCollection.equals(Collection.INTERACTION)){
						currentCollection = Collection.INTERACTION;
						showCollection(currentCollection);
					}
				} else {
					// The book at front has been selected with a given collection id. 
					// We want to move it to another the collection. 
					if (command.contains("1") && !currentCollection.equals(Collection.INFERENCE)){
						currentCollection = Collection.INFERENCE;
//						moveActiveObjectToCurrentCollectionAndShow(Collection.INFERENCE);
						moveToCollection = Collection.INFERENCE;
					} else if (command.contains("2") && !currentCollection.equals(Collection.DYNAMICS)){
						currentCollection = Collection.DYNAMICS;
//						moveActiveObjectToCurrentCollectionAndShow(Collection.DYNAMICS);
						moveToCollection = Collection.DYNAMICS;
					} else if (command.contains("3") && !currentCollection.equals(Collection.INTERACTION)){
						currentCollection = Collection.INTERACTION;
//						moveActiveObjectToCurrentCollectionAndShow(Collection.INTERACTION);
						moveToCollection = Collection.INTERACTION;
					}
				}
			}
			break;
		case HOTSPOT_OFF:
//			currentCollection = Collection.ALL;
//			showCollection(currentCollection);
			break;
		default:
			break;
		}
	}
	
	public void moveActiveObjectToCurrentCollectionAndShow(Collection moveToCollection) {
		final String bookName = dbAdapter.getTitleforID(activeObject.getName());
		final String category = dbAdapter.getCollectionforID(activeObject.getName()).getName();
//		handleMessage(currentCollection + " Moving book object with title " + bookName + " and category " 
//			+ category + " to " + moveToCollection);
		
		dbAdapter.moveToCollection(Integer.parseInt((activeObject).getName()), 
				moveToCollection);
	    log.writeWTime(String.format("Book %s moved from %s to %s ", bookName, category, moveToCollection.getName()));
	    Log.d(LOG, String.format("Book %s moved from %s to %s ", bookName, category, moveToCollection.getName()));
		
		if (bookToFind != null && categoryToMove != null) {
			if (bookToFind.getBookName().equals(bookName) && categoryToMove.equals(moveToCollection.getName())) {
				// Correct.
				BookshelfActivity.this.runOnUiThread(new Runnable() {
					@Override public void run() {
						new Timer().schedule(new TimerTask(){
						@Override public void run() {
							Looper.prepare();
							renderer.selectedToBack();
						}
					}, 1000);
						experiment.doNextTask();	
					}
				});
			}
		} 
//		new Timer().schedule(new TimerTask(){
//			@Override public void run() {
//				renderer.selectedToBack();
//			}
//		}, 1000);
		
		renderer.setVisibility(currentCollection.equals(moveToCollection) || 
				currentCollection.equals(Collection.ALL));
		if (!inPhoneOnlyMode) {
			showCollection(currentCollection);
		}
		// TODO(norrielm): See what this is like and then delete if unused.
//		renderer.selectedToBack();
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		if (renderer == null || scaleDetector == null || mGestureDetector.onTouchEvent(event)) {
			return true;
		}
		// Let the ScaleGestureDetector inspect all events.
		scaleDetector.onTouchEvent(event);
		final int action = event.getAction();
		switch (action & MotionEvent.ACTION_MASK) {
		case MotionEvent.ACTION_DOWN: {
			// TODO(norrielm): Remove the kinect swiping logic if unused.
			log.writeWTime("Finger touching");
		    Log.d(LOG, "Finger touching");
		    fingerTouching = true;
		    
			// Store pointer position and Id
			final float x = event.getX();
			final float y = event.getY();
			xpos = x;
			ypos = y;
			activePointerId = event.getPointerId(0);
			// Test whether an object is being touched by the user.		
			activeObject = renderer.getObjectAtPosition((int) event.getX(), (int) event.getY());
			break;
		}
		case MotionEvent.ACTION_MOVE: {
			if (longpress) {
//				renderer.moveObject((int) event.getX(), (int) event.getY());
				break;	
			}

			final int pointerIndex = event.findPointerIndex(activePointerId);
			final float x = event.getX(pointerIndex);
			final float y = event.getY(pointerIndex);

			// Only move if the ScaleGestureDetector isn't processing a gesture.
			if (!scaleDetector.isInProgress()) {
				final float dx = x - xpos;
				final float dy = y - ypos;
				xpos += dx;
				ypos += dy;
			}
			xpos = x;
			ypos = y;
			break;
		}
		case MotionEvent.ACTION_UP:
			activePointerId = INVALID_POINTER_ID;
			if (longpress) {
				renderer.addSelectedObjectPosition();
			} else if (longpressFront && moveToCollection != null) {
				moveActiveObjectToCurrentCollectionAndShow(moveToCollection);
				moveToCollection = null;
			}
			fingerTouching = false;
			longpress = false;
			longpressFront = false;
			break;
		case MotionEvent.ACTION_CANCEL:
			activePointerId = INVALID_POINTER_ID;
			longpress = false;
			fingerTouching = false;
			break;
		case MotionEvent.ACTION_POINTER_UP:
			final int pointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) 
				>> MotionEvent.ACTION_POINTER_INDEX_SHIFT;		            
			int pointerId = event.getPointerId(pointerIndex);
			if (pointerId == activePointerId) {
				// This was our active pointer going up. Choose a new
				// active pointer and adjust accordingly.
				final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
				xpos = event.getX(newPointerIndex);
				ypos = event.getY(newPointerIndex);
				activePointerId = event.getPointerId(newPointerIndex);
			}
			fingerTouching = false;
			break;
		}
		return false;
	}

	@Override
	public void onAccuracyChanged(Sensor sensor, int accuracy) {
		// Unused.
	}

	@Override
	public void onSensorChanged(SensorEvent event) {
		float alpha = 0.8f;
		float [] gravity = new float [3];
		float [] linear_acceleration = new float [3]; 

		gravity[0] = alpha * gravity[0] + (1 - alpha) * event.values[0];
		gravity[1] = alpha * gravity[1] + (1 - alpha) * event.values[1];
		gravity[2] = alpha * gravity[2] + (1 - alpha) * event.values[2];

		linear_acceleration[0] = event.values[0] - gravity[0];
		linear_acceleration[1] = event.values[1] - gravity[1];
		linear_acceleration[2] = event.values[2] - gravity[2];

		// Scroll according to the device orientation.
		Display display = ((WindowManager) getSystemService(Context.WINDOW_SERVICE))
		.getDefaultDisplay();
		if (display.getRotation() == Surface.ROTATION_90) {
			if (linear_acceleration [1] < -0.5){
				scrollRight(4);
			} else if (linear_acceleration [1] > 0.5){
				scrollLeft(4);
			}
		} else if (display.getRotation() == Surface.ROTATION_270) {
			if (linear_acceleration [1] < -0.5){
				scrollLeft(4);
			} else if (linear_acceleration [1] > 0.5){
				scrollRight(4);
			}
		}
	}

	/**
	 * Listener to scale objects when a scale gesture is performed.
	 */
	// TODO(norrielm): Factor these gesture classes into a package.
	private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
		@Override
		public boolean onScale(ScaleGestureDetector detector) {
			scaleFactor *= detector.getScaleFactor();
			if (scaleFactor > 2.5f){
				//get referenced object
//				Object3D bookToOpen = activeObject;
//				if (bookToOpen != null) {
//					String bookId = bookToOpen.getName();
//					activeObjectUrl = dbAdapter.getURLforID(Integer.parseInt(bookId));
//					// Advise GLLayer to show animation once if Book is atFront
//					if (manager.atFront.contains(bookToOpen)) {
//						renderer.showOpenAnimation();
//					}
//				}
				scaleFactor = 1.0f;
			}
			return true;
		}
	}

	public void scrollLeft(int distance) {
		if (renderer != null && !fingerTouching) {
			renderer.moveCameraLeft(distance);
		}
	}

	public void scrollRight(int distance) {
		if (renderer != null && !fingerTouching) {
			renderer.moveCameraRight(distance);
		}
	}

//	public void openBook() {
//		Object3D open = activeObject;
//		if (open != null){
//			String id = open.getName();            	
//			activeObjectUrl = dbAdapter.getURLforID(Integer.parseInt(id));
//			renderer.showOpenAnimation();
//		}
//	}

	public Collection getCurrentCollection() {
		return currentCollection;
	}

	/** 
	 * Gesture listener for tapped objects.
	 */
	private class ExtendedGestureListener extends GestureDetector.SimpleOnGestureListener {

		@Override
		public void onShowPress(MotionEvent event) {

		}

		@Override
		public void onLongPress(MotionEvent event) {
			xpos = event.getX();
			ypos = event.getY();
			activeObject = renderer.getObjectAtPosition((int) event.getX(), (int) event.getY());
			if (activeObject != null) {
				if (!manager.atFront.contains(activeObject)) {
					// TODO(norrielm): factor the tactile feedback out.
					longpress = true;
					longpressFront = false;
				} else {
					v.vibrate(100);
					longpress = false;
					longpressFront = true;
					// Only show categorise menu when in phone-only mode.
					if (inPhoneOnlyMode){
						// launch Menu
						loadMoveDialog((activeObject).getName());
					}
				}
			}
		}

		@Override
		public boolean onDoubleTap(MotionEvent event) {
			Log.d(LOG, "onDoubleTap " + event.toString());
			// TODO(norrielm): manage this state in a method.
			longpress = false;
			longpressFront = false;
			fingerTouching = false;
			// TODO(norrielm): factor this test into a method.
			activeObject = renderer.getObjectAtPosition((int) event.getX(), (int) event.getY());
			if (activeObject != null) {
				Object3D current = activeObject;
				final String bookName = dbAdapter.getTitleforID(current.getName());
				Collection category = dbAdapter.getCollectionforID(current.getName());
//				handleMessage("Double tapped book object with title " + bookName + " and category " 
//					+ category);
				log.writeWTime("Selected book " + category.getName());
			    Log.d(LOG, "Selected book " + category.getName());
				
				if(manager.atFront.contains(current)){
					renderer.selectedToBack();
				} else {
					renderer.bringSelectedToFront();
				}
				
				if (bookToFind != null && categoryToMove == null) {
					new Timer().schedule(new TimerTask(){
						@Override public void run() {
							renderer.selectedToBack();
						}
					}, 1000);
					if (bookToFind.getBookName().equals(bookName)) {
						// Correct.
						BookshelfActivity.this.runOnUiThread(new Runnable() {
							@Override public void run() {
								experiment.doNextTask();	
							}
						});
					}
				}
			}
			return true;
		}

		@Override
		public boolean onSingleTapUp(MotionEvent event) {
//			Log.d(LOG, "onSingleTapUp " + event.toString());
			Object3D obj = renderer.getObjectAtPosition((int) event.getX(), (int) event.getY());
			if (obj == null) {
				log.writeWTime("Single tapped");
			    Log.d(LOG, "Single tapped");	
			} else {
				String bookName = dbAdapter.getTitleforID(obj.getName());
				log.writeWTime("Single tapped book " + bookName);
			    Log.d(LOG, "Single tapped book " + bookName);
			}			
			xpos = -1;
			ypos = -1;
			renderer.setTouchTurn(0);
			renderer.setTouchTurnUp(0);
			activeObject = null;
			longpress = false;
			longpressFront = false;
			fingerTouching = false;
			return true;
		}

		@Override
		public boolean onScroll(MotionEvent downEvent, MotionEvent moveEvent, float distanceX, 
				float distanceY) {
			xpos = downEvent.getX();
			ypos = downEvent.getY();
			activeObject = renderer.getObjectAtPosition((int) downEvent.getX(), 
				(int) downEvent.getY());
			if (activeObject != null) {
//				renderer.setTouchTurn(distanceX / 100f);
//				renderer.setTouchTurnUp(distanceY / 100f);
			}
			return true;
		}

		@Override 
		public boolean onDown(MotionEvent me) {
			return false;
		}

		@Override
		public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, 
				float velocityY) {
			return false;
		}
	}

	@Override
	public void exit() {
		// TODO(norrielm): Implement me.		
	}

	@Override
	public void handleDisconnect() {
		// TODO(norrielm): Implement me.		
	}
	
	/**
     * Create and display a dialog that allows the user to select the type of hotspot they wish to create.
	 * @param string 
     */
    // TODO(marionkoelle): Could pull these dialogs out into a helper class.
	// TODO(marionkoelle): define CharSequence globally
	Collection moveToBookCollection;
	final String[] collectionNames = {"INFERENCE", "DYNAMICS", "INTERACTION"};
    public void loadMoveDialog(final String objectID){
		log.writeWTime("Move dialog displaying for object " + objectID);
	    Log.d(LOG, "Move dialog displaying for object " + objectID);
    	Collection currentBookCollection = dbAdapter.getCollectionforID(objectID);
    	moveToBookCollection = currentBookCollection;
		AlertDialog.Builder d = new AlertDialog.Builder(this);
		d.setTitle("Move to collection...")
		.setCancelable(true)
		.setSingleChoiceItems(collectionNames, currentBookCollection.ordinal()-1, 
			new DialogInterface.OnClickListener() {
				public void onClick(DialogInterface dialog, int id) {
					// Ignore the index of ALL in Collections.
					moveToBookCollection = Collection.values()[id + 1];
				}
		})
		.setPositiveButton("OK", 
		new DialogInterface.OnClickListener() {
			public void onClick(DialogInterface dialog, int id) {
				switch(moveToBookCollection) {
				case INFERENCE:
//					currentCollection = moveToBookCollection;
					moveActiveObjectToCurrentCollectionAndShow(moveToBookCollection);
//					dbAdapter.moveToCollection(Integer.parseInt(objectID),Collection.INFERENCE);
//					showCollection(Collection.INFERENCE);
					//renderer.selectedToBack();
//					handleMessage("Current collection :" + Collection.INFERENCE + "!");
					break;
				case DYNAMICS:
					//currentCollection = moveToBookCollection;
					moveActiveObjectToCurrentCollectionAndShow(moveToBookCollection);
//					dbAdapter.moveToCollection(Integer.parseInt(objectID),Collection.DYNAMICS);
//					showCollection(Collection.DYNAMICS);
					//renderer.selectedToBack();
//					handleMessage("Current collection :" + Collection.DYNAMICS + "!");
					break;
				case INTERACTION:
//					currentCollection = moveToBookCollection;
					moveActiveObjectToCurrentCollectionAndShow(moveToBookCollection);
//					dbAdapter.moveToCollection(Integer.parseInt(objectID), Collection.INTERACTION);
//					showCollection(Collection.INTERACTION);
					//renderer.selectedToBack();
//					handleMessage("Current collection :" + Collection.INTERACTION + "!");
					break;
				default:
					log.writeWTime("Menu cancelled");
				    Log.d(LOG, "Menu cancelled");
				    break;
				}
				dialog.cancel();
			}
		})
		.setNeutralButton("Cancel", 
			new DialogInterface.OnClickListener() {
				public void onClick(DialogInterface dialog,
						int id) {
					dialog.cancel();
				}
			});
		d.create();
		d.show();
    }
}
